//
//  NetManager.swift
//  SReader
//
//  Created by JunMing on 2021/6/18.
//  Copyright © 2021 赵俊明. All rights reserved.
//

import UIKit
import Alamofire
import Foundation

struct NetManager {
    public typealias Completion<Result> = (ComplateStatus<Result>) -> Void
    public enum ComplateStatus<Result> {
        case Success(Result)
        case Fail(Int, Any?)
        case Error(Int)
        case Cancel
    }
    
    public enum ConnectionType: String {
        case POST = "POST"
        case GET = "GET"
        case DEL = "DELETE"
        case PUT = "PUT"
    }
    
    public enum QueueType: String, CaseIterable {
        case Normal = "rootQueue"
        case Authorization = "authQueue"
    }
    
    // MARK: 设置超时时间
    public static let TIMEOUT_INTERVAL: TimeInterval = 100
    // MARK: 数据格式
    private static let dateFormat: DateFormatter = DateFormatter()
    private static var sessionsDic: [QueueType: Session] = [QueueType: Session]()
    // MARK: - ---Common Function---
    private static func getALSession(_ type: QueueType = .Normal) -> Session {
        guard let alamofireSession: Session = sessionsDic[type] else {
            return configure(type)
        }
        return alamofireSession
    }
    
    private static func configure(_ resultSessionType: QueueType) -> Session {
        var currentSession: Session!
        QueueType.allCases.forEach {
            let configuration: URLSessionConfiguration = URLSessionConfiguration.default
            configuration.httpAdditionalHeaders = URLSessionConfiguration.af.default.httpAdditionalHeaders
            configuration.timeoutIntervalForRequest = TIMEOUT_INTERVAL
            configuration.timeoutIntervalForResource = TimeInterval(60 * 2)
            let alamofireSession = Session(configuration: configuration,
                                           delegate: SessionDelegate(),
                                           rootQueue: DispatchQueue(label: "com.sreader.connectorManager.\($0.rawValue)"),
                                           interceptor: RequestHandler())
            sessionsDic.updateValue(alamofireSession, forKey: $0)
            if $0 == resultSessionType {
                currentSession = alamofireSession
            }
        }
        
        dateFormat.locale = Locale(identifier: "en_US_POSIX")
        dateFormat.timeZone = TimeZone(identifier: "Asia/Shanghai")
        return currentSession
    }
    
    /// 取消所有请求
    public static func cancleAllConnection() {
        sessionsDic.forEach {
            $0.value.session.getAllTasks { (taskList) in
                taskList.forEach { (task) in
                    task.cancel()
                }
            }
        }
    }
    
    /// 取消特定请求
    public static func cancelConnection(serverURLArray: [String]) {
        sessionsDic.forEach {
            $0.value.session.getAllTasks {
                $0.forEach {
                    if let aCurrentUELString = $0.currentRequest?.url?.absoluteString,
                       serverURLArray.filter({ aCurrentUELString.contains($0) }).count > 0 {
                        $0.cancel()
                    }
                }
            }
        }
    }
    
    // 获取代理状态
    public static func getProxyStatus() -> Bool {
        if let dicRef = CFNetworkCopySystemProxySettings()?.takeUnretainedValue() {
            // kCFNetworkProxiesHTTPProxy
            let key = Unmanaged.passRetained(kCFNetworkProxiesHTTPProxy as NSString).autorelease().toOpaque()
            if let proxyCFstr = CFDictionaryGetValue(dicRef, key) {
                let result = Unmanaged<NSString>.fromOpaque(proxyCFstr).takeUnretainedValue()
                print("代理名字：\(result)")
                return true
            }
        }
        return false
    }

    /// 发起请求方法
    public static func request<PostData: Encodable>(url: String,
                                                            method: ConnectionType = .GET,
                                                            headers: HTTPHeaders? = nil,
                                                            postData: PostData? = nil,
                                                            encoder: ParameterEncoder? = nil,
                                                            queueType: QueueType = .Normal,
                                                            timeout: TimeInterval = TIMEOUT_INTERVAL,
                                                            completion: Completion<Data>?) {
        
        let complateBlack: (AFDataResponse<Data>) -> Void = { (response: AFDataResponse<Data>) in
            autoreleasepool {
                let statusCode: Int = response.response?.statusCode ?? 0
                switch response.error {
                case let error where error == nil:
                    if let data = response.data {
                        completion?(.Success(data))
                    }
                case .sessionTaskFailed(let error) where (error as NSError).code == -999:
                    print("Cancel api: \(url)")
                    completion?(.Cancel)
                default:
                    let errorLogger = "----------------------------------------\nRequest: \(url)\nMethod: \(method)\nHttpCode: \(statusCode)\nHeader: \(String(describing: response.request?.headers))\nData: \(String(describing: postData))\nError: \(String(describing: response.error?.localizedDescription))"
                    print(errorLogger)
                    if ![0, 500].contains(statusCode) {
                        completion?(.Fail(statusCode, errorLogger))
                    } else {
                        completion?(.Error(statusCode))
                    }
                }
            }
        }
        
        switch method {
        case .GET:
            getALSession(queueType).streamRequest(url,
                                            method: .get,
                                            parameters: postData,
                                            encoder: encoder ?? URLEncodedFormParameterEncoder.default,
                                            headers: headers) { $0.timeoutInterval = timeout }
                .validate()
                .responseStream(stream: { stream in
                    
                })
                // .reponseHandle(completionHandler: complateBlack)
            
            
            getALSession(queueType).request(url,
                                            method: .get,
                                            parameters: postData,
                                            encoder: encoder ?? URLEncodedFormParameterEncoder.default,
                                            headers: headers) { $0.timeoutInterval = timeout }
                .validate()
                .reponseHandle(completionHandler: complateBlack)
        default:
            getALSession(queueType).request(url,
                                            method: HTTPMethod(rawValue: method.rawValue),
                                            parameters: postData,
                                            encoder: encoder ?? JSONParameterEncoder.default,
                                            headers: headers) { $0.timeoutInterval = timeout }
                .validate()
                .reponseHandle(completionHandler: complateBlack)
        }
    }
    
    /// 下载
    @discardableResult
    public static func download(url: String,
                                headers: HTTPHeaders? = nil,
                                progress: ((Double) -> Void)?,
                                queueType: QueueType = .Normal,
                                completion: Completion<URL>?) -> DownloadRequest {
        return getALSession(queueType).download(url, headers: headers, to:  { (targetPath: URL, response: HTTPURLResponse) -> (destinationURL: URL, options: DownloadRequest.Options) in
            autoreleasepool {
                var tempPath: String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
                tempPath.append("/books/")
                if let filename: String = response.suggestedFilename {
                    return (URL(fileURLWithPath: tempPath).appendingPathComponent(filename), [.removePreviousFile, .createIntermediateDirectories])
                }
                return (targetPath, [])
            }
        }).downloadProgress { (downProgress) in
            autoreleasepool {
                progress?(downProgress.fractionCompleted)
            }
        }.response { (downloadResponse: AFDownloadResponse<URL?>) in
            autoreleasepool {
                let statusCode: Int = downloadResponse.response?.statusCode ?? 0
                let failBlock: (() -> Void) = {
                    print("Fail download: \(url)")
                    if let filePath: URL = downloadResponse.fileURL {
                        try? FileManager.default.removeItem(at: filePath)
                    }
                    completion?(.Error(statusCode))
                }
                
                switch downloadResponse.error {
                case let error where error == nil:
                    if statusCode == 200 {
                        if let filePath: URL = downloadResponse.fileURL {
                            completion?(.Success(filePath))
                        } else {
                            failBlock()
                        }
                    } else {
                        failBlock()
                    }
                case .sessionTaskFailed(let error) where (error as NSError).code == -999:
                    print("Cancel api: \(url)")
                    completion?(.Cancel)
                default:
                    failBlock()
                }
            }
        }
    }
    
    /// 上传
    public static func uploadFile(url: String,
                                  fileObject: Any,
                                  name: String? = nil,
                                  headers: HTTPHeaders? = nil,
                                  postData: [String: String]? = nil,
                                  progress: ((Double) -> Void)? = nil,
                                  queueType: QueueType = .Normal,
                                  completion: Completion<Data>?) {
        getALSession(queueType).upload(multipartFormData: { (formData: MultipartFormData) in
            autoreleasepool {
                postData?.forEach({ (key: String, value: String) in
                    if let data: Data = value.data(using: .utf8) {
                        formData.append(data, withName: key)
                    }
                })
                
                switch fileObject {
                case let url as URL:
                    formData.append(url, withName: "file")
                case let image as UIImage:
                    if let imageData: Data = image.pngData(), let imageName = name {
                        formData.append(imageData, withName: "file", fileName: imageName, mimeType: "image/jpeg")
                    }
                default:
                    break
                }
            }
        }, to: url, headers: headers) { _ in
        }.uploadProgress { (uploadProgress: Progress) in
            autoreleasepool {
                progress?(uploadProgress.fractionCompleted)
            }
        }.response { (response: AFDataResponse<Data?>) in
            autoreleasepool {
                if let resultData = response.data,
                   let statusCode: Int = response.response?.statusCode {
                    switch response.error {
                    case let error where error == nil:
                        completion?(.Success(resultData))
                    case .sessionTaskFailed(let error) where (error as NSError).code == -999:
                        completion?(.Cancel)
                    default:
                        print("Upload \(url) Error\nError: \(String(describing: response.error?.localizedDescription))\nResponse:")
                        if ![0, 500].contains(statusCode) {
                            completion?(.Fail(statusCode, nil))
                        } else {
                            completion?(.Error(statusCode))
                        }
                    }
                } else {
                    completion?(.Error(0))
                }
            }
        }
    }
}

extension NetManager {    
    static public func requestModel<PostData: Encodable, Model: Codable>(url: HttpRequest,
                                                                                    method: ConnectionType = .GET,
                                                                                    headers: HTTPHeaders? = nil,
                                                                                    postData: PostData? = nil,
                                                                                    encoder: ParameterEncoder? = nil,
                                                                                    queueType: QueueType = .Normal,
                                                                                    timeout: TimeInterval = TIMEOUT_INTERVAL,
                                                                                    completion: Completion<Model>?) {
        request(url: url.url,
                method: method,
                headers: headers,
                postData: postData,
                encoder: encoder,
                queueType: queueType,
                timeout: timeout) { (result: ComplateStatus<Data>) in
            switch result {
            case .Success(let data):
                DispatchQueue.global(qos: .default).async {
                    do {
                        let model = try JSONDecoder().decode(Model.self, from: data)
                        DispatchQueue.main.async {
                            completion?(.Success(model))
                        }
                    } catch let jsonError {
                        print(jsonError)
                    }
                }
            case .Fail(let errorCode, let json):
                completion?(.Fail(errorCode, json))
            case .Error(let errorCode):
                completion?(.Error(errorCode))
            case .Cancel:
                completion?(.Cancel)
            }
        }
    }
    
    static public func upload<Model: Codable>(url: HttpRequest,
                                                                   objc: Any,
                                                                   name: String? = nil,
                                                                   headers: HTTPHeaders? = nil,
                                                                   postData: [String: String]? = nil,
                                                                   progress: ((Double) -> Void)? = nil,
                                                                   queueType: QueueType = .Normal,
                                                                   completion: Completion<Model>?) {
        
        uploadFile(url: url.url,
                   fileObject: objc,
                   name: name,
                   headers: headers,
                   postData: postData,
                   progress: progress,
                   queueType: queueType,
                   completion: { result in
            switch result {
            case .Success(let data):
                DispatchQueue.global(qos: .default).async {
                    do {
                        let model = try JSONDecoder().decode(Model.self, from: data)
                        DispatchQueue.main.async {
                            completion?(.Success(model))
                        }
                    } catch let jsonError {
                        print(jsonError)
                    }
                }
            case .Fail(let errorCode, let json):
                completion?(.Fail(errorCode, json))
            case .Error(let errorCode):
                completion?(.Error(errorCode))
            case .Cancel:
                completion?(.Cancel)
            }
        })
    }
}
